package com.k8.common.aop;

import com.k8.common.aop.internal.advice.Advice;
import com.k8.common.aop.internal.advisor.Advisor;
import com.k8.common.aop.internal.interceptor.MethodInterceptor;
import com.k8.common.aop.internal.interceptor.ProxyMethodExecutor;
import com.k8.common.aop.internal.pointcut.PointCut;
import com.k8.common.util.AopUtil;
import com.k8.common.util.ClassUtil;
import lombok.extern.slf4j.Slf4j;

import java.io.Serializable;
import java.lang.reflect.Method;
import java.util.*;

/*
 * support为bean层面，先验证存在再传入
 * 单例检测放到creatBean处
 * */

@Slf4j
public class AdvisedSupport implements Serializable {
    protected static final ThreadLocal<Map<String, List<MethodInterceptor>>> cacheCandidates = new ThreadLocal<>();
    private final Class<?> targetClass;
    private Object target;//要代理的对象
    private final ClassLoader classLoader;
    private Object proxy;

    /*
     * 使用cacheCandidates作用
     * 1.在多线程动态代理不会受影响，只有保证检验和实例是同一个线程
     * 2.先检验是否需要代理，如果需要则先存到local
     * 3.根据检验结果决定是否实例
     * */
    private Map<String, List<MethodInterceptor>> cacheAdvices = new HashMap<>();

    public Class<?> getTargetClass() {
        return targetClass;
    }

    public Object getTarget() {
        return target;
    }

    public Map<String, List<MethodInterceptor>> getCacheAdvices() {
        return cacheAdvices;
    }

    public AdvisedSupport(Object target) {
        this.target = target;
        this.targetClass = target.getClass();
        Map<String, List<MethodInterceptor>> candidates = cacheCandidates.get();
        if (candidates == null) throw new RuntimeException("EXECUTE isProxyNecessary");
        cacheAdvices = candidates;
        this.classLoader = ClassUtil.getClassLoader(targetClass);
        cacheCandidates.remove();
    }

    public ClassLoader getClassLoader() {
        return classLoader;
    }

    public List<MethodInterceptor> getChain(Method method) {
        List<MethodInterceptor> interceptors = null;
        String methodKey = ClassUtil.getMethodToString(method);
        //ClassUtil.getFullMethodKey()
        interceptors = cacheAdvices.get(methodKey);

        return interceptors;
    }

    public Object getProxy() {
        return proxy;
    }

    public void setProxy(Object proxy) {
        this.proxy = proxy;
    }

    protected static MethodInterceptor transferToInterceptor(Advice advice) {
        //todo 如果新建切面需要拓展转换方法
        if (advice instanceof ProxyMethodExecutor pe) {
            return pe;
        } else {
            log.debug("advice is not a methodInterceptor");
        }
        return null;
    }

    public static boolean isProxyNecessary(Object target, List<Advisor> advisors) {
        Class<?> clazz = target.getClass();
        return isProxyNecessary(clazz, advisors);
    }

    public static boolean isProxyNecessary(Class<?> clazz, List<Advisor> advisors) {
        if (clazz.getName().startsWith("java") || clazz.isInterface()) return false;
        Map<String, List<MethodInterceptor>> tempMap = new TreeMap<>();
        boolean tag = isProxyNecessary0(clazz, advisors, tempMap);
        Class<?>[] interfaces = clazz.getInterfaces();
        for (Class<?> anInterface : interfaces) {
            boolean proxyNecessary = isProxyNecessary0(anInterface, advisors, tempMap);
            if (!tag) {
                tag = proxyNecessary;
            }
        }
        Map<String, List<MethodInterceptor>> caches = null;
        if (AdvisedSupport.cacheCandidates.get() != null) {
            caches = AdvisedSupport.cacheCandidates.get();
        }
        if (!tempMap.isEmpty()) {
            Set<String> methodKeys = tempMap.keySet();
            for (String methodKey : methodKeys) {
                List<MethodInterceptor> methodInterceptors = tempMap.get(methodKey);
                if (!methodInterceptors.isEmpty()) {
                    if (methodInterceptors.size() > 1) {
                        methodInterceptors.sort(new Comparator<MethodInterceptor>() {
                            @Override
                            public int compare(com.k8.common.aop.internal.interceptor.MethodInterceptor o1, com.k8.common.aop.internal.interceptor.MethodInterceptor o2) {
                                int result;
                                if (o1.getAdviceOrder() != o2.getAdviceOrder()) {
                                    result = o1.getAdviceOrder() - o2.getAdviceOrder();
                                } else {
                                    result = o1.getOrder() - o2.getOrder();
                                }
                                return result;
                            }
                        });
                    }
                    tag = true;
                    if (caches == null) {
                        caches = new HashMap<>();
                        AdvisedSupport.cacheCandidates.set(caches);
                    }
                    caches.put(methodKey, methodInterceptors);
                }
            }
        }
        return tag;
    }

    /**
     * 监测方法是否有通知中对应的方法，将匹配到的转换成拦截器放到tempMap中
     *
     * @param clazz
     * @param advisors
     * @param tempMap
     * @return
     */
    private static boolean isProxyNecessary0(Class<?> clazz, List<Advisor> advisors, Map<String, List<MethodInterceptor>> tempMap) {
        boolean tag = false;
        for (Method method : clazz.getMethods()) {
            if (AopUtil.isNoOpMethod(method)) {//静态，final，hashCode，toString
                continue;
            }
            List<MethodInterceptor> methodInterceptors = advisors.stream().filter(advisor -> {
                        PointCut pointCut = advisor.getPointCut();
                        if (pointCut.match(method)) {
                            return true;
                        }
                        return false;
                    }).map(Advisor::getAdvice)
                    .map(AdvisedSupport::transferToInterceptor)
                    .filter(Objects::nonNull)
                    .toList();
            String methodKey = ClassUtil.getMethodToString(method);
            List<MethodInterceptor> interceptors = tempMap.get(methodKey);
            if (interceptors == null) {
                tempMap.put(methodKey, methodInterceptors);
            } else {
                interceptors.addAll(methodInterceptors);
            }
        }
        return tag;
    }
}
